import Singleton from "../Base/Singleton";
import {
  ApiMsgEnum,
  IModel,
  binaryDecode,
  binaryEncode,
  strdecode,
  strencode,
} from "../Common";

interface IItem {
  cb: Function;
  ctx: unknown;
}

interface ICallApiRet<T> {
  success: boolean;
  error?: Error;
  res?: T;
}

export default class NetworkManager extends Singleton {
  static get Instance() {
    return super.GetInstance<NetworkManager>();
  }

  isConnected: boolean = false;
  port = 9876;
  ws: WebSocket;

  private map: Map<ApiMsgEnum, Array<IItem>> = new Map();

  connect() {
    return new Promise((resolve, reject) => {
      if (this.isConnected) {
        resolve(true);
        return;
      }

      this.ws = new WebSocket("ws://localhost:" + this.port);
      this.ws.binaryType = "arraybuffer";

      this.ws.onopen = () => {
        this.isConnected = true;
        resolve(true);
      };
      this.ws.onclose = () => {
        this.isConnected = false;
        reject(false);
      };
      this.ws.onmessage = (msg) => {
        console.log("ws onmsg", msg.data);
        this.handleMsg(msg);
      };
      this.ws.onerror = (e) => {
        this.isConnected = false;
        console.log(e.toString());
        reject(false);
      };
    });
  }

  callApi<T extends keyof IModel["api"]>(
    name: T,
    data: IModel["api"][T]["req"]
  ): Promise<ICallApiRet<IModel["api"][T]["res"]>> {
    return new Promise((resolve) => {
      try {
        const timer = setTimeout(() => {
          resolve({ success: false, error: new Error("Timeout") });
          this.unlistenMsg(name as any, cb, null);
        }, 5000);

        const cb = (res) => {
          resolve(res);
          clearTimeout(timer);
          this.unlistenMsg(name as any, cb, null);
        };
        this.listenMsg(name as any, cb, null);
        this.sendMsg(name as any, data as any);
      } catch (err) {
        resolve({ success: false, error: new Error(err) });
      }
    });
  }

  async sendMsg<T extends keyof IModel["msg"]>(
    name: T,
    data: IModel["msg"][T]
  ) {
    // await new Promise((resolve) => {
    //   setTimeout(resolve, 2000);
    // });

    // const msg = { name, data };
    // const str = JSON.stringify(msg);
    // const ta = strencode(str);
    // const ab = new ArrayBuffer(ta.length);
    // const dv = new DataView(ab);
    // for (let i = 0; i < ta.length; i++) {
    //   dv.setUint8(i, ta[i]);
    // }

    const bc = binaryEncode(name, data);

    this.ws.send(bc.buffer);
  }

  handleMsg(msg) {
    try {
      // const ta = new Uint8Array(msg.data);
      // const str = strdecode(ta);
      // let json = JSON.parse(str);

      const json = binaryDecode(msg.data);

      const { name, data } = json;

      if (this.map.has(name)) {
        this.map.get(name).forEach(({ cb, ctx }) => {
          cb.call(ctx, data);
        });
      }
    } catch (e) {
      console.log(e);
    }
  }

  listenMsg<T extends keyof IModel["msg"]>(
    name: T,
    cb: (args: IModel["msg"][T]) => void,
    ctx: unknown
  ) {
    if (this.map.has(name)) {
      this.map.get(name).push({ cb, ctx });
    } else {
      this.map.set(name, [{ cb, ctx }]);
    }
  }

  unlistenMsg<T extends keyof IModel["msg"]>(
    name: T,
    cb: (args: IModel["msg"][T]) => void,
    ctx: unknown
  ) {
    if (this.map.has(name)) {
      const index = this.map
        .get(name)
        .findIndex((i) => cb === i.cb && i.ctx === ctx);
      index > -1 && this.map.get(name).splice(index, 1);
    }
  }
}
